perm filename LET1[F82,JMC] blob
sn#734060 filedate 1983-12-01 generic text, type C, neo UTF8
COMMENT ⊗ VALID 00003 PAGES
C REC PAGE DESCRIPTION
C00001 00001
C00002 00002 let1[f82,jmc] A call-by-need let macro
C00008 00003 examples
C00012 ENDMK
C⊗;
let1[f82,jmc] A call-by-need let macro
Let exp{exp1} be a pure Lisp expression that contains
exp1 as a subexpression occurring several times. We can ensure
that exp1 is computed at most once by writing
((lambda (x) exp{x}) exp1)
or
(let ((x exp1)) exp{x}).
However, this may not be optimal because exp1 may be part of a
conditional expression and may not have to be computed at all if
the propositions don't require it.
The conventional modern solution to this problem is to
package exp1 into an object or actor or whatever the formalism
calls for. When it is first called, it modifies itself, so that
it gives the answer right away at any subsequent calls. We may
also use a call-by-need interpreter and then lambda is ok. All of
these solutions involves local state, and this is problematical
for the theory. Another possibility is to have the program
explicitly pass the value of exp1 along to the next use. This
makes for obscure programs. We are about to propose a third
solution, and the term project is to implement it.
We need the notion of an inevitable subexpression of
an expression. We say that exp1 is inevitable in exp if
exp1 is always evaluated in the course of evaluating exp.
In this case, there is no problem; the above-mentioned lambda
expression or let expression will work. Suppose exp1 is
not inevitable. Then it is because exp1 occurs in a conditional
expression within exp, not all branches of which contain it.
Case 1. exp is a conditional expression of the form
if p then exp2 else exp3,
and exp1 occurs only in exp2 and is inevitable in exp2, so
we write exp2 as exp2{exp1}. This is easily handled; we replace
exp by
if p then [λx.exp2{x}][exp1] else exp3
or by the corresponding expression using let.
If exp1 occurs only in exp2 but is not inevitable in exp2,
then we must dig deeper, i.e. the replacement by a λ-expression
will occur a a deeper level. If exp itself isn't a conditional
expression but the conditional expression is inevitable in exp,
the replacement occurs as before. If exp1 is inevitable in both
exp2 and exp3 or is inevitable in p, then it is inevitable in
the whole conditional expression, which can then be rewritten
[λx.if p{x} then exp2{x} else exp3{x}][exp1].
It may happen that exp1 occurs in both exp2 and exp3 but
isn't inevitable in both. In this case both have to be transformed.
The interesting case is when exp1 occurs in p but is
not inevitable in p and also occurs in exp2 or exp3.
Then exp1 may have to be evaluated in the course of computing p,
and the value may have to be saved for the subsequent computations.
This can only happen if p itelf contains a conditional expression.
Using the distributive law for conditional expressions and functions,
this internal conditional expression can come to the top of p,
so that we now have something of the form
if (if q then r else s) then exp1 else exp2.
where exp1 occur in q, r, or s with the same subcases as before
including this case. We then use one of the rules for transforming
conditional expressions to get
if q then (if r then exp2 else exp3) else (if s then exp2 else exp3)
which reduces the complexity of the problem, i.e. the only interesting
case is when exp1 occurs in q but not inevitably. After some
number of transformations, we are back at an easy case.
All this can be done automatically by a macro we shall call
let-by-need. We write
let-by-need[[x←exp1] exp{x}]
or in internal notation
(let-by-need ((x exp1)) exp{x}),
and the let-by-need macro performs all the required expansions.
Problem due December 8
Write the let-by-need macro and test it.
;;; examples
frmak x ← <x>
frnull x ← n x
frcar x ← if at a x then a x else a gopher a x
frcdr x ← if at a x then nil else <d gopher a x>
or
frmak x ← if at x then <x> else <gopher x>
frnull x ← n x
frcar x ← if at a x then a x else aa x
frcdr x ← if at a x then nil <gopher da x>
samefringe[x,y] ← same1[frmak x,frmak y]
same1[u,v] ← frnull u ∧ frnull v
∨ ¬frnull u ∧ ¬frnull v ∧ frcar u eq frcar v ∧ same1[frcdr u,frcdr v]
(defun frmak (x) (list x))
(defun frnull (x) (null x))
(defun frcar (x) (if (atom (car x)) (car x) (car (gopher (car x)))))
(defun frcdr (x) (if (atom (car x)) nil (list (cdr (gopher (car x))))))
(defun frmak1 (x) (if (atom x) (list x) (list (gopher x))))
(defun frnull1 (x) (null x))
(defun frcar1 (x) (if (atom (car x)) (car x) (caar x)))
(defun frcdr1 (x) (if (atom (car x))
nil
(if (atom (cdar x))
(list (cdar x))
(list (gopher (cdar x))))))
(defun gopher (xx) (if (atom (car xx))
xx
(gopher (cons (caar xx) (cons (cdar xx) (cdr xx))))))
(defun samefringe (x y) (same (frmak x) (frmak y)))
(defun same (u v)
(or (and (frnull u)
(frnull v))
(and (not (frnull u))
(not (frnull v))
(eq (frcar u) (frcar v))
(same (frcdr u) (frcdr v)))))
(defun samefringe1 (x y) (same1 (frmak1 x) (frmak1 y)))
(defun same1 (u v)
(or (and (frnull1 u)
(frnull1 v))
(and (not (frnull1 u))
(not (frnull1 v))
(eq (frcar1 u) (frcar1 v))
(same1 (frcdr1 u) (frcdr1 v)))))
(defun gopher2 (x)
(if (or (atom x) (atom (car x)))
x
(gopher2 (cons (caar x) (cons (cdar x) (cdr x))))))
(defun frmak2 (x) (gopher2 x))
(defun frnull2 (x) (equal x '((nil))))
(defun frcar2 (x) (if (atom x) x (car x)))
(defun frcdr2 (x) (if (atom x) '((nil)) (gopher2 (cdr x))))
(defun samefringe2 (x y) (same2 (frmak2 x) (frmak2 y)))
(defun same2 (u v)
(or (and (frnull2 u)
(frnull2 v))
(and (not (frnull2 u))
(not (frnull2 v))
(eq (frcar2 u) (frcar2 v))
(same2 (frcdr2 u) (frcdr2 v)))))